///////////////////////////////////////////////////////////////////////////////
//
// DBT86 Project : x86 Platform : x86 Interpreter : ALU instructions
// Copyright (C) 2004 by Ben Hertzberg and Stanford University
// All Rights Reserved
//
// This code may not be used for any purpose without the express written
// consent of the author.  See the LICENSE.txt file for details.
//
//
// DESCRIPTION
//
// This unit implements interpretation of x86 FPU ops using the softfloat
// library.
//
///////////////////////////////////////////////////////////////////////////////

#include "interp.h"
#if CFG_CODEGEN
#include "cpu.h"
#endif

// TODO: shouldn't need this include (math should be ops on CPU model)
#include "softfloat.h"
#include "math.h"

// TODO: obscure the library stuff behind CPU routines

#define FPR_ABS_IDX(idx) ((X86_CTXT(FPtop) + (idx)) & (X86_NUM_FPR-1))
#define FP_TAG_SETVALID(idx) ( X86_CTXT(FPtag) &= ~(3 << (2 * FPR_ABS_IDX(idx))) )
#define FP_TAG_CLEARVALID(idx) ( X86_CTXT(FPtag) |= (3 << (2 * FPR_ABS_IDX(idx))) )
#define FP_TAG_ISVALID(idx) ( ((X86_CTXT(FPtag) >> (2 * FPR_ABS_IDX(idx))) & 3 ) != 3 )

// we just ignore stack underflow instead of emulating the resulting status bits
#define FPR_LOAD(idx) ( X86_CTXT(FPR[FPR_ABS_IDX(idx)]) )
// #define FPR_LOAD(idx) ( ASSERT(FP_TAG_ISVALID(idx)), X86_CTXT(FPR[FPR_ABS_IDX(idx)]) )
#define FPR_LOAD_REG FPR_LOAD(FPR_REG_SPEC)

#if defined(CFG_DEBUG) || !defined(LESS86)
#define FP_SET_SHADOW(idx,v) (X86_CTXT(FPRshadow[FPR_ABS_IDX(idx)]) = floatx80_to_double(v))
#else
#define FP_SET_SHADOW(idx,v) 0
#endif

#define FP_SET_REG(idx,v) ( FP_SET_SHADOW(idx,v), FP_TAG_SETVALID(idx), X86_CTXT(FPR[FPR_ABS_IDX(idx)]) = v )

#define FPR_REG_SPEC (X86_TMP(rm))

#define FP_LOAD_MEM_32 FP_CVT_32_80(FP_BITS32(LOAD_VIRTUAL_UNALIGNED_DWORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ)))
#define FP_LOAD_MEM_64 FP_CVT_64_80(FP_BITS64(LOAD_VIRTUAL_UNALIGNED_QWORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ)))
#define FP_LOAD_MEM_80 FpuLoadMem80()
#define FP_LOAD_MEM_I2 FP_CVT_I2_80(LOAD_VIRTUAL_UNALIGNED_WORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ))
#define FP_LOAD_MEM_I4 FP_CVT_I4_80(LOAD_VIRTUAL_UNALIGNED_DWORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ))
#define FP_LOAD_MEM_I8 FP_CVT_I8_80(LOAD_VIRTUAL_UNALIGNED_QWORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ))


floatx80 floatx80_subr(floatx80 a, floatx80 b) { return floatx80_sub(b,a); }
floatx80 floatx80_divr(floatx80 a, floatx80 b) { return floatx80_div(b,a); }

double floatx80_to_double(floatx80 x) {
    FP64U u;
    u.bits = FP_CVT_80_64(x);
    return u.value;
}

FP80 FpuLoadMem80() {
  FP80 res;
  res.low = LOAD_VIRTUAL_UNALIGNED_QWORD(X86_TMP(va),X86_TMP(segOverride),ACCESS_READ);
  res.high = LOAD_VIRTUAL_UNALIGNED_WORD(X86_TMP(va)+8,X86_TMP(segOverride),ACCESS_READ);
  return res;
}

FP80 FP_c_1   = { 0x8000000000000000ULL, 0x3FFF };
FP80 FP_c_l2t = { 0xD49A784BCD1B8AFEULL, 0x4000 };
FP80 FP_c_l2e = { 0xB8AA3B295C17F0BCULL, 0x3FFF };
FP80 FP_c_pi  = { 0xC90FDAA22168C235ULL, 0x4000 };
FP80 FP_c_lg2 = { 0x9A209A84FBCFF799ULL, 0x3FFD };
FP80 FP_c_ln2 = { 0xB17217F7D1CF79ACULL, 0x3FFE };
FP80 FP_c_0   = { 0x0000000000000000ULL, 0x0000 };

// TODO: use this level of indirection to implement rounding modes
#define FP_C_1 FP_c_1
#define FP_C_L2T FP_c_l2t
#define FP_C_L2E FP_c_l2e
#define FP_C_PI FP_c_pi
#define FP_C_LG2 FP_c_lg2
#define FP_C_LN2 FP_c_ln2
#define FP_C_0 FP_c_0

void FpuSetControlWord(UINT v) {
  UINT ctrl;

  X86_CTXT(FPcontrol) = (v & 0x1F3F) | 0x40;

  // interface to softfloat
  ctrl = (v & 0x1F3F) | 0x40;
  float_rounding_mode = (ctrl >> 10) & 0x3;
  // TODO: denormal operand exceptions?
  float_exception_flags = ctrl & 0x3F;
  switch ((ctrl >> 8) & 0x3) {
    case 0:
    case 1:
      floatx80_rounding_precision = 32;
      break;
    case 2:
      floatx80_rounding_precision = 64;
      break;
    case 3:
      floatx80_rounding_precision = 80;
      break;
  }

}

bool FpuGetReg(UINT idx, void *buf) {
  if (FP_TAG_ISVALID(idx)) {
    FP80 v;
    v = FPR_LOAD(idx);
    *(volatile UINT64*)buf = v.low;
    *(volatile UINT16*)(((char*)buf)+8) = v.high;
    return true;
  }
  else {
    return false;
  }
}


#define FP_SET_CONTROL(v) FpuSetControlWord(v)


#define FP_OP(op, src1, src2, dst_stk_idx, stk_chk) { \
      FP80 v1, v2, res; \
      v1 = src1; \
      v2 = src2; \
      res = op(v1,v2); \
      FP_SET_REG(dst_stk_idx, res); \
    }

#define FP_OP1(op, src, dst_stk_idx, stk_chk) { \
      FP80 v, res; \
      v = src; \
      res = op(v); \
      FP_SET_REG(dst_stk_idx, res); \
    }


// TODO: make sure we have correct semantics for "invalid" operand cases
// TODO: add correct semantics for all FP ops to check various flags before operation for possible exception

#define FP_COM(src1,src2,unordered, sets_integer_flags) { \
      int invalid, equality, less_than; \
      /* TODO: invalid calc */ \
      invalid = 0; \
      if (!unordered && invalid) { \
        /* TODO: exception */ \
        ASSERT(UNREACHED); \
      } \
      equality = floatx80_eq(src1,src2); \
      less_than = floatx80_lt(src1,src2); \
      if (sets_integer_flags) { \
        IVAL flags = getFlags() & ~(EFLAGS_BIT_C | EFLAGS_BIT_Z | EFLAGS_BIT_P); \
        if (invalid) { \
          flags |= (EFLAGS_BIT_C | EFLAGS_BIT_Z | EFLAGS_BIT_P); \
        } else { \
          flags |= (equality ? EFLAGS_BIT_Z : 0); \
          flags |= (less_than ? EFLAGS_BIT_C : 0); \
        } \
        setFlags(flags); \
      } else { \
        X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); \
        if (invalid) { \
          X86_CTXT(FPstatus) |= (invalid ? (FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C0) : 0); \
        } else { \
          X86_CTXT(FPstatus) |= (equality ? FP_STATUS_C3 : 0); \
          X86_CTXT(FPstatus) |= (less_than ? FP_STATUS_C0 : 0); \
        } \
      } \
    }

#define FP_PUSH(x) { \
      FP80 v = x; \
      ASSERT(!FP_TAG_ISVALID(-1)); \
      X86_CTXT(FPtop) = FPR_ABS_IDX(-1); \
      FP_SET_REG(0,v); \
    }

#define FP_POP() { \
      ASSERT(FP_TAG_ISVALID(0)); \
      FP_TAG_CLEARVALID(0); \
      X86_CTXT(FPtop) = FPR_ABS_IDX(1); \
    }

// 2 numbers at the end are: dest_stack_index, stack_check


void FADD_f()    { FP_OP(FP_ADD,  FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }
void FMUL_f()    { FP_OP(FP_MUL,  FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }
void FSUB_f()    { FP_OP(FP_SUB,  FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }
void FSUBR_f()   { FP_OP(FP_SUBR, FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }
void FDIV_f()    { FP_OP(FP_DIV,  FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }
void FDIVR_f()   { FP_OP(FP_DIVR, FPR_LOAD(0) , FPR_LOAD_REG  , 0           , FPR_REG_SPEC); }

void FADD_r()    { FP_OP(FP_ADD,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }
void FMUL_r()    { FP_OP(FP_MUL,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }
void FSUB_r()    { FP_OP(FP_SUB,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }
void FSUBR_r()   { FP_OP(FP_SUBR, FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }
void FDIV_r()    { FP_OP(FP_DIV,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }
void FDIVR_r()   { FP_OP(FP_DIVR, FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); }

void FADDP_r()   { FP_OP(FP_ADD,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }
void FMULP_r()   { FP_OP(FP_MUL,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }
void FSUBP_r()   { FP_OP(FP_SUB,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }
void FSUBRP_r()  { FP_OP(FP_SUBR, FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }
void FDIVP_r()   { FP_OP(FP_DIV,  FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }
void FDIVRP_r()  { FP_OP(FP_DIVR, FPR_LOAD_REG, FPR_LOAD(0)   , FPR_REG_SPEC, FPR_REG_SPEC); FP_POP(); }

void FPREM1()    { FP_OP(FP_REM,  FPR_LOAD(0) , FPR_LOAD(1)   , 0           , 1); }
void FSQRT()     { FP_OP1(FP_SQRT,FPR_LOAD(0)                 , 0           , 0); }

void FADD_m32()  { FP_OP(FP_ADD,  FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }
void FMUL_m32()  { FP_OP(FP_MUL,  FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }
void FSUB_m32()  { FP_OP(FP_SUB,  FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }
void FSUBR_m32() { FP_OP(FP_SUBR, FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }
void FDIV_m32()  { FP_OP(FP_DIV,  FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }
void FDIVR_m32() { FP_OP(FP_DIVR, FPR_LOAD(0) , FP_LOAD_MEM_32, 0           , 0); }

void FADD_m64()  { FP_OP(FP_ADD,  FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }
void FMUL_m64()  { FP_OP(FP_MUL,  FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }
void FSUB_m64()  { FP_OP(FP_SUB,  FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }
void FSUBR_m64() { FP_OP(FP_SUBR, FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }
void FDIV_m64()  { FP_OP(FP_DIV,  FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }
void FDIVR_m64() { FP_OP(FP_DIVR, FPR_LOAD(0) , FP_LOAD_MEM_64, 0           , 0); }

void FIADD_m32() { FP_OP(FP_ADD,  FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }
void FIMUL_m32() { FP_OP(FP_MUL,  FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }
void FISUB_m32() { FP_OP(FP_SUB,  FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }
void FISUBR_m32(){ FP_OP(FP_SUBR, FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }
void FIDIV_m32() { FP_OP(FP_DIV,  FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }
void FIDIVR_m32(){ FP_OP(FP_DIVR, FPR_LOAD(0) , FP_LOAD_MEM_I4, 0           , 0); }

void FIADD_m16() { FP_OP(FP_ADD,  FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }
void FIMUL_m16() { FP_OP(FP_MUL,  FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }
void FISUB_m16() { FP_OP(FP_SUB,  FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }
void FISUBR_m16(){ FP_OP(FP_SUBR, FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }
void FIDIV_m16() { FP_OP(FP_DIV,  FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }
void FIDIVR_m16(){ FP_OP(FP_DIVR, FPR_LOAD(0) , FP_LOAD_MEM_I2, 0           , 0); }

// FP compares have no destination reg, but have an extra 2 flags (unordered, sets_integer_flags)
void FCOM_f()    { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               0, 0); }
void FCOMP_f()   { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               0, 0); FP_POP(); }
void FUCOMI()    { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               1, 1); }
void FCOMI()     { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               0, 1); }
void FUCOM_f()   { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               1, 0); }
void FUCOMP_f()  { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               1, 0); FP_POP(); }
void FUCOMIP()   { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               1, 1); FP_POP(); }
void FCOMIP()    { FP_COM( FPR_LOAD(0) , FPR_LOAD_REG  ,               0, 1); FP_POP(); }
void FCOMPP()    { FP_COM( FPR_LOAD(0) , FPR_LOAD(1)   ,               0, 0); FP_POP(); FP_POP(); }
void FUCOMPP()   { FP_COM( FPR_LOAD(0) , FPR_LOAD(1)   ,               1, 0); FP_POP(); FP_POP(); }
void FCOM_m32()  { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_32,               0, 0); }
void FCOMP_m32() { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_32,               0, 0); FP_POP(); }
void FCOM_m64()  { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_64,               0, 0); }
void FCOMP_m64() { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_64,               0, 0); FP_POP(); }
void FICOM_m32() { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_I4,               0, 0); }
void FICOMP_m32(){ FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_I4,               0, 0); FP_POP(); }
void FICOM_m16() { FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_I2,               0, 0); }
void FICOMP_m16(){ FP_COM( FPR_LOAD(0) , FP_LOAD_MEM_I2,               0, 0); FP_POP(); }
void FTST()      { FP_COM( FPR_LOAD(0) , FP_C_0        ,               0, 0); }


void FLD_f()     { FP_PUSH(FPR_LOAD_REG); }
void FLD_m32()   { FP_PUSH(FP_LOAD_MEM_32); }
void FLD_m64()   { FP_PUSH(FP_LOAD_MEM_64); }
void FLD_m80()   { FP_PUSH(FP_LOAD_MEM_80); }
void FILD_m16()  { FP_PUSH(FP_LOAD_MEM_I2); }
void FILD_m32()  { FP_PUSH(FP_LOAD_MEM_I4); }
void FILD_m64()  { FP_PUSH(FP_LOAD_MEM_I8); }
void FLD1()      { FP_PUSH(FP_C_1);         }
void FLDL2T()    { FP_PUSH(FP_C_L2T);       }
void FLDL2E()    { FP_PUSH(FP_C_L2E);       }
void FLDPI()     { FP_PUSH(FP_C_PI);        }
void FLDLG2()    { FP_PUSH(FP_C_LG2);       }
void FLDLN2()    { FP_PUSH(FP_C_LN2);       }
void FLDZ()      { FP_PUSH(FP_C_0);         }


void FST_f()     { FP_SET_REG(FPR_REG_SPEC, FPR_LOAD(0)); }
void FSTP_f()    { FP_SET_REG(FPR_REG_SPEC, FPR_LOAD(0)); FP_POP(); }
void FST_m32()   { FP80 v80 = FPR_LOAD(0); FP32 v; v = FP_CVT_80_32(v80); STORE_RM_DWORD(FP_BITS32(v)); }
void FSTP_m32()  { FP80 v80 = FPR_LOAD(0); FP32 v; v = FP_CVT_80_32(v80); STORE_RM_DWORD(FP_BITS32(v)); FP_POP(); }
void FST_m64()   { FP80 v80 = FPR_LOAD(0); FP64 v; v = FP_CVT_80_64(v80); STORE_RM_QWORD(FP_BITS64(v)); }
void FSTP_m64()  { FP80 v80 = FPR_LOAD(0); FP64 v; v = FP_CVT_80_64(v80); STORE_RM_QWORD(FP_BITS64(v)); FP_POP(); }
// TODO: check access on all 80 bits prior to performing even partial access (or otherwise guarantee that both roll back together)
void FSTP_m80() { FP80 v = FPR_LOAD(0); STORE_VIRTUAL_UNALIGNED_QWORD(v.low,X86_TMP(va),X86_TMP(segOverride),0); STORE_VIRTUAL_UNALIGNED_WORD(v.high,X86_TMP(va)+8,X86_TMP(segOverride),0);  FP_POP(); }

void FIST_m16()  { FP80 v80 = FPR_LOAD(0); INT16 v = FP_CVT_80_I2(v80); STORE_RM_WORD(v); }
void FISTP_m16() { FP80 v80 = FPR_LOAD(0); INT16 v = FP_CVT_80_I2(v80); STORE_RM_WORD(v); FP_POP(); }
void FIST_m32()  { FP80 v80 = FPR_LOAD(0); INT32 v = FP_CVT_80_I4(v80); STORE_RM_DWORD(v); }
void FISTP_m32() { FP80 v80 = FPR_LOAD(0); INT32 v = FP_CVT_80_I4(v80); STORE_RM_DWORD(v); FP_POP(); }
void FISTP_m64() { FP80 v80 = FPR_LOAD(0); INT64 v = FP_CVT_80_I8(v80); STORE_RM_QWORD(v); FP_POP(); }

void FXCH_f() { FP80 v = FPR_LOAD(0); FP_SET_REG(0, FPR_LOAD(FPR_REG_SPEC)); FP_SET_REG(FPR_REG_SPEC, v); X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);}

void FCHS() { FP80 v = FPR_LOAD(0); v.high ^= 0x8000; FP_SET_REG(0, v); X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);}

void FABS() { FP80 v = FPR_LOAD(0); v.high &= ~0x8000; FP_SET_REG(0, v); X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);}

void FRNDINT() { FP80 v = FPR_LOAD(0); v = FP_RND_INT(v); FP_SET_REG(0,v); X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);}

void FNOP() { /* do nothing */ }

void FSTSW_m16() { STORE_RM_WORD(X86_CTXT(FPstatus)); }

void FSTSW() { STORE_REG_ID_WORD(AX,X86_CTXT(FPstatus)); }

// TODO: float tininess after rounding... what is the proper setting?
void FLDCW() {
  IVAL m;
  UINT v;
  LOAD_RM_R_WORD(m);
  v = (UINT)m;
  FP_SET_CONTROL(v);
}

void FSTCW() { 
  STORE_RM_WORD(X86_CTXT(FPcontrol));
}


// TODO: find out if we can treat this as a NOP
void FWAIT() {
  // no work; all FP exceptions are already precise
}

// TODO: FP ops
void FXTRACT() {
  FP80 t = FPR_LOAD(0);
  FP80 v = t;
  v.high |= 0x3FFF;
  ASSERT(UNREACHED); // more to be done but instructions are fuzzy
}

// TODO: flesh out this implementation beyond this hack
void FXAM() {
  FP80 v;
  X86_CTXT(FPstatus) = X86_CTXT(FPstatus) & ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);
  v = FPR_LOAD(0);
  if (v.high & 0x8000) {
    X86_CTXT(FPstatus) |= FP_STATUS_C1;
  }
  if (!FP_TAG_ISVALID(0)) {
    X86_CTXT(FPstatus) |= (FP_STATUS_C3 | FP_STATUS_C0);
  } else if (0) { // TODO: isUnsupported
    // nothing to do
  } else if (0) { // TODO: isNaN
    X86_CTXT(FPstatus) |= FP_STATUS_C0;
  } else if (0) { // TODO: isInfinity
    X86_CTXT(FPstatus) |= (FP_STATUS_C2 | FP_STATUS_C0);
  } else if (0) { // TODO: isDenormal
    X86_CTXT(FPstatus) |= (FP_STATUS_C3 | FP_STATUS_C2);
  } else if (floatx80_eq(v,FP_C_0)) {
    X86_CTXT(FPstatus) |= FP_STATUS_C3;
  } else  {
    X86_CTXT(FPstatus) |= FP_STATUS_C2;
  }
}

void FSCALE() {
  // TODO: handle special situations delicately, error checking, stack checks, etc.
  FP80 v = FPR_LOAD(0);
  if (floatx80_eq(v,FP_C_0)) {
    // nothing happens when you scale zero
  } else {
    INT scale = FP_CVT_80_I4(FPR_LOAD(1));
    v.high += scale;
  }
  X86_CTXT(FPstatus) &= ~FP_STATUS_C1;
  FP_SET_REG(0,v);
}

// TODO: exp cleanup
void F2XM1() { 
  FP64U u; 
  u.bits = FP_CVT_80_64(FPR_LOAD(0)); 
  u.value = pow(2.0, u.value) - 1.0; 
  FP_SET_REG(0,FP_CVT_64_80(u.bits)); 
  X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);
}
void FYL2X() {
  FP64U v0, v1; 
  v0.bits = FP_CVT_80_64(FPR_LOAD(0)); 
  v1.bits = FP_CVT_80_64(FPR_LOAD(1));
  v1.value = v1.value * (log(v0.value)/log((double)2.0));
  FP_SET_REG(1,FP_CVT_64_80(v1.bits)); 
  X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);
  FP_POP();
}
void FYL2XP1() {
  FP64U v0, v1; 
  v0.bits = FP_CVT_80_64(FPR_LOAD(0)); 
  v1.bits = FP_CVT_80_64(FPR_LOAD(1));
  v1.value = v1.value * (log(v0.value + 1.0)/log((double)2.0));
  FP_SET_REG(1,FP_CVT_64_80(v1.bits)); 
  X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0);
  FP_POP();
}

// TODO: FP trig cleanup, corner cases (these are really just hacks)
void FPTAN() { FP64U u; u.bits = FP_CVT_80_64(FPR_LOAD(0)); u.value = tan(u.value); FP_SET_REG(0,FP_CVT_64_80(u.bits)); FP_PUSH(FP_C_1); X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); }
void FPATAN() { FP64U u,v; u.bits = FP_CVT_80_64(FPR_LOAD(0)); v.bits = FP_CVT_80_64(FPR_LOAD(1)); u.value = atan2(v.value,u.value); FP_SET_REG(1,FP_CVT_64_80(u.bits)); FP_POP(); X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); }
void FSINCOS() { FP64U u,v; u.bits = FP_CVT_80_64(FPR_LOAD(0)); v.value = sin(u.value); u.value = cos(u.value); FP_SET_REG(0,FP_CVT_64_80(v.bits)); FP_PUSH(FP_CVT_64_80(u.bits)); X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); }
void FSIN() { FP64U u; u.bits = FP_CVT_80_64(FPR_LOAD(0)); u.value = sin(u.value); FP_SET_REG(0,FP_CVT_64_80(u.bits)); X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); }
void FCOS() { FP64U u; u.bits = FP_CVT_80_64(FPR_LOAD(0)); u.value = cos(u.value); FP_SET_REG(0,FP_CVT_64_80(u.bits)); X86_CTXT(FPstatus) &= ~(FP_STATUS_C3 | FP_STATUS_C2 | FP_STATUS_C1 | FP_STATUS_C0); }

#define FCMOV_OP(cond_evaluator) { \
      FP80 src; \
      src = FPR_LOAD_REG; \
      if (cond_evaluator) { \
        FP_SET_REG(0, src); \
      } \
    }

void FCMOVB() { FCMOV_OP(COND_EVAL_B); }
void FCMOVE() { FCMOV_OP(COND_EVAL_E); }
void FCMOVBE() { FCMOV_OP(COND_EVAL_BE); }
void FCMOVU() { FCMOV_OP(COND_EVAL_P); }

void FCMOVNB() { FCMOV_OP(!(COND_EVAL_B)); }
void FCMOVNE() { FCMOV_OP(!(COND_EVAL_E)); }
void FCMOVNBE() { FCMOV_OP(!(COND_EVAL_BE)); }
void FCMOVNU() { FCMOV_OP(!(COND_EVAL_P)); }

// TODO: FP state mgmt
void FDECSTP() { ASSERT(UNREACHED); }
void FINCSTP() { ASSERT(UNREACHED); }

void FFREE_f() { FP_TAG_CLEARVALID(FPR_REG_SPEC); }

void FFREEP() { 
    FFREE_f(); 
    // FP_POP(); without the ASSERT(FP_TAG_ISVALID(0));
    FP_TAG_CLEARVALID(0);
    X86_CTXT(FPtop) = FPR_ABS_IDX(1);
}

/**
   Figure 7-14. Real Mode FPU State Image in Memory, 32-Bit Format
*/
#define SIZE_OF_FPU_STATE 28
typedef struct {
    unsigned controlWord:16;
    unsigned reserved1:16;
    unsigned statusWord:16;
    unsigned reserved2:16;
    unsigned tagWord:16;
    unsigned reserved3:16;
    unsigned fpuInstructionPointer_15_00:16;
    unsigned reserved4:16;
    unsigned opcode_10_0:11;
    unsigned zero1:1;
    unsigned fpuInstructionPointer_31_16:16;
    unsigned zero2:4;
    unsigned operandPointer_15_00:16;
    unsigned reserved5:16;
    unsigned zero3:12;
    unsigned operandPointer_31_16:16;
    unsigned zero4:4;
} FPUState_t;

void FCLEX() { ASSERT(UNREACHED); }
void FINIT() { 
    // assert is here to make sure it is run at startup
    ASSERT(sizeof(FPUState_t) == SIZE_OF_FPU_STATE);
    FpuSetControlWord(0x037f);
    X86_CTXT(FPstatus) = 0;
    X86_CTXT(FPtag) = 0xffff;
    X86_CTXT(FPtop) = 0;
}


void FLDENV() { ASSERT(UNREACHED); }
void FSTENV() { ASSERT(UNREACHED); }

void FRSTOR() { 
    FPUState_t fpustate;
    uint8* fpustatePtr = (uint8*)&fpustate;
    uint i = 0;
    for (;i < sizeof(FPUState_t); i+= sizeof(uint32)) {
        fpustatePtr[i] = LOAD_VIRTUAL_UNALIGNED_DWORD(X86_TMP(va)+i,
                                                      X86_TMP(segOverride),
                                                      ACCESS_READ);
    }
    for (int f = 0; f < X86_NUM_FPR; f++, i+= 80/8) {
        X86_CTXT(FPR[f]).low  = LOAD_VIRTUAL_UNALIGNED_QWORD(X86_TMP(va)+i  ,X86_TMP(segOverride),ACCESS_READ);
        X86_CTXT(FPR[f]).high = LOAD_VIRTUAL_UNALIGNED_WORD (X86_TMP(va)+i+8,X86_TMP(segOverride),ACCESS_READ);
    }

    X86_CTXT(FPcontrol) = fpustate.controlWord;
    X86_CTXT(FPstatus)  = fpustate.statusWord;
    X86_CTXT(FPtag)     = fpustate.tagWord;
    //FPUDataPointer;
    //FPUInstructionPointer;
    //FPULastInstructionOpcode;
}
void FSAVE() { 
    FPUState_t fpustate;
    fpustate.controlWord = X86_CTXT(FPcontrol);
    fpustate.statusWord  = X86_CTXT(FPstatus);
    fpustate.tagWord     = X86_CTXT(FPtag);
    //FPUDataPointer;
    //FPUInstructionPointer;
    //FPULastInstructionOpcode;
    fpustate.zero1 = 0;
    fpustate.zero2 = 0;
    fpustate.zero3 = 0;
    fpustate.zero4 = 0;
    uint8* fpustatePtr = (uint8*)&fpustate;
    uint i = 0;
    for (; i < sizeof(FPUState_t); i+= sizeof(uint32)) {
        STORE_VIRTUAL_UNALIGNED_DWORD(fpustatePtr[i],
                                      X86_TMP(va+i),
                                      X86_TMP(segOverride),
                                      ACCESS_WRITE);
    }
    for (int f = 0; f < X86_NUM_FPR; f++, i+= 80/8) {
        STORE_VIRTUAL_UNALIGNED_QWORD(X86_CTXT(FPR[f]).low, X86_TMP(va)+i  ,X86_TMP(segOverride),ACCESS_WRITE);
        STORE_VIRTUAL_UNALIGNED_WORD(X86_CTXT(FPR[f]).high, X86_TMP(va)+i+8,X86_TMP(segOverride),ACCESS_WRITE);
    }
    FINIT();
}

// TODO: odd instructions
void FBLD() { ASSERT(UNREACHED); }
void FBSTP() { ASSERT(UNREACHED); }
void FPREM() { FP_OP(FP_REM,  FPR_LOAD(0) , FPR_LOAD(1)   , 0           , 1); }
